home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / scheme / boxer / boxer.lha / pbox.lisp < prev    next >
Encoding:
Text File  |  1993-07-17  |  45.2 KB  |  1,129 lines

  1. ;;; -*-Package: (PBOX GLOBAL 1000); Base:8.; Mode:lisp-*-
  2.  
  3. ;;; (C) Copyright 1983 Massachusetts Institute of Technology
  4. ;;;
  5. ;;; Permission to use, copy, modify, distribute, and sell this software
  6. ;;; and its documentation for any purpose is hereby granted without fee,
  7. ;;; provided that the above copyright notice appear in all copies and that
  8. ;;; both that copyright notice and this permission notice appear in
  9. ;;; supporting documentation, and that the name of M.I.T. not be used in
  10. ;;; advertising or publicity pertaining to distribution of the software
  11. ;;; without specific, written prior permission.  M.I.T. makes no
  12. ;;; representations about the suitability of this software for any
  13. ;;; purpose.  It is provided "as is" without express or implied warranty.
  14. ;;;
  15. ;;; serial character printer for boxes.
  16. ;;; this code is meant to run in both MacLisp and Zetalisp.
  17.  
  18. ;;; The box printer is divided into several parts.  The printer prints
  19. ;;; printable-box-objects, which are generated by the preprocessor.  The
  20. ;;; preprocessor itself is divided into two parts, the reader (which reads box
  21. ;;; files and conses up a printable-box-object, assuming no constraints), and
  22. ;;; the fitter, sometimes referred to in what follows as Procrustes, which
  23. ;;; operates on the printable-box-object and outputs a list of
  24. ;;; printable-box-objects, each of which is guaranteed to fit within the width
  25. ;;; of a page and be self-consistent.  The fitter has a list of tools, some of
  26. ;;; which are the exporter and the breaker (not implemented).  The printer is
  27. ;;; called by the page generator, which outputs individual pages, does
  28. ;;; formfeeds, numbers the boxes and pages, etc.
  29.  
  30. ;;; In order to make this run in MacLisp, I define a string datatype, which is a
  31. ;;; list whose second member is the symbol STRING, first member a tail-pointer
  32. ;;; (for STRING-NCONC) and the rest of which is a series of fixnums representing
  33. ;;; the characters in the string.  STRING comes second, not first, because it
  34. ;;; becomes hard to print empty strings when the tail pointer contains the tail
  35. ;;; pointer.  The normal MacLisp excuse for strings is not used, because it
  36. ;;; would involve a great deal of copying.
  37.  
  38. #M
  39. (DEFMACRO STRINGP (STRING)
  40.     ;;validate the tail pointer somewhat, but don't take too long.
  41.     `(IF (AND (LISTP ,STRING) (CAR ,STRING) (LISTP (CAR ,STRING))
  42.           (CDR ,STRING) (EQ (CADR ,STRING) 'STRING))
  43.      T
  44.        NIL))
  45.  
  46. #M
  47. (DEFUN STRING-LENGTH (STRING)
  48.     (IF (STRINGP STRING) (LENGTH (CDDR STRING))
  49.       (FERROR NIL "The argument to STRING-LENGTH, ~S was not a string."
  50.           STRING)))
  51.  
  52. #M
  53. (DEFUN STRING (OBJECT)
  54.     (COND ((STRINGP OBJECT) OBJECT)
  55.       ((SYMBOLP OBJECT) (LEXPR-FUNCALL #'MAKE-STRING (EXPLODEN OBJECT)))
  56.       ((FIXNUMP OBJECT) (MAKE-STRING OBJECT))
  57.       (T (FERROR NIL "The argument to STRING, ~S, cannot be coerced ~
  58. to a string." OBJECT))))
  59.  
  60. ;;; to be called from code that's already done the type-check
  61. #M
  62. (DEFMACRO TAIL-POINTER (STRING)
  63.     `(CAR ,STRING))
  64.  
  65. #Q
  66. (DEFMACRO TAIL-POINTER (STRING)
  67.     `(STRING-LENGTH ,STRING))
  68.  
  69. #M
  70. (DEFMACRO SET-TAIL-POINTER (STRING LIST)
  71.     `(SETF (TAIL-POINTER ,STRING) ,LIST))
  72.  
  73. ;;; return a pointer to the beginning of a string.
  74. #M
  75. (DEFUN START-POINTER (STRING)
  76.     (IF (STRINGP STRING) (CDR STRING)
  77.       (FERROR NIL "The argument to START-POINTER, ~S, was not a string."
  78.           STRING)))
  79.  
  80. #Q
  81. (DEFMACRO START-POINTER (IGNORE) 0)
  82.  
  83. #M
  84. (DEFMACRO CHAR-AT-POINTER (POINTER IGNORE)
  85.     `(CADR ,POINTER))
  86.  
  87. #Q
  88. (DEFMACRO CHAR-AT-POINTER (POINTER STRING)
  89.     `(AREF ,STRING ,POINTER))
  90.     
  91. (DEFMACRO GET-CHAR-AND-ADVANCE-POINTER (POINTER STRING)
  92.   `(PROG1 (CHAR-AT-POINTER ,POINTER ,STRING)
  93.       (ADVANCE-POINTER ,POINTER)))
  94.  
  95. (DEFUN POINTER-POINTS-TO-END? (POINTER STRING)
  96.   (IF (STRINGP STRING) (EQ POINTER (TAIL-POINTER STRING))
  97.     (FERROR NIL "The second argument to POINTER-POINTS-TO-END?, ~S,
  98. was not a string." STRING)))
  99.  
  100. #M
  101. (DEFMACRO ADVANCE-POINTER (POINTER)
  102.     `(SETQ ,POINTER (CDR ,POINTER)))
  103.  
  104. #Q
  105. (DEFMACRO ADVANCE-POINTER (POINTER)
  106.     `(INCF ,POINTER))
  107.  
  108. #M
  109. (DEFUN MAKE-STRING (&REST ELEMENTS)
  110.     ;; make sure all the elements are fixnums.
  111.     (DO ((ELEMENTS ELEMENTS (CDR ELEMENTS)))
  112.     ((NULL ELEMENTS))
  113.       (IF (NOT (FIXNUMP (CAR ELEMENTS)))
  114.       (FERROR NIL "One of the arguments to MAKE-STRING, ~S, was ~
  115. not a fixnum." (CAR ELEMENTS))))
  116.       ;; okay to return a REST list in MacLisp.
  117.     (LET ((NEW-STRING (CONS NIL (CONS 'STRING ELEMENTS))))
  118.       ;; calling LAST on elements would break if no elements.
  119.       (SET-TAIL-POINTER NEW-STRING (LAST NEW-STRING))
  120.       NEW-STRING))
  121.  
  122. #Q
  123. (DEFUN MAKE-STRING (&REST ELEMENTS)
  124.     ;; make sure all the elements are characters
  125.     (DO ((ELEMENTS ELEMENTS (CDR ELEMENTS)))
  126.     ((NULL ELEMENTS))
  127.       (IF (NOT (FIXNUMP (CAR ELEMENTS)))
  128.       (FERROR NIL "The object ~S is not a fixnum." (CAR ELEMENTS))))
  129.     (LET* ((LENGTH (LENGTH ELEMENTS))
  130.        (STRING (MAKE-ARRAY LENGTH ':TYPE 'ART-STRING
  131.                    ':LEADER-LIST (LIST LENGTH))))
  132.     (FILLARRAY STRING ELEMENTS)))      ;FILLARRAY returns STRING
  133.  
  134. #M
  135. (DEFUN CHARACTER (STRING)
  136.     (CHAR-AT-POINTER (START-POINTER STRING) STRING))
  137.  
  138. #M
  139. (DEFUN STRING-EQUAL (STRING1 STRING2)
  140.     (EQUAL (STRING STRING1) (STRING STRING2)))
  141.  
  142. #M
  143. (DEFUN STRING-NCONC (STRING1 STRING2)
  144.     (COND ((FIXNUMP STRING2)
  145.        (LET ((NEW-TAIL (NCONS STRING2)))
  146.          (RPLACD (TAIL-POINTER STRING1) NEW-TAIL)
  147.          (SET-TAIL-POINTER STRING1 NEW-TAIL)))
  148.       ((STRINGP STRING2)
  149.        (RPLACD (TAIL-POINTER STRING1) (CDDR STRING2))
  150.        (SET-TAIL-POINTER STRING1 (TAIL-POINTER STRING2)))
  151.       (T (FERROR NIL "The second argument to STRING-NCONC, ~S, ~
  152. was not a string or a fixnum."))))
  153.  
  154. ;;; copies top-level elements.
  155. #M
  156. (DEFUN SUBLIST (LIST START &OPTIONAL END)
  157.     (DO ((LIST (NTHCDR START LIST) (CDR LIST))
  158.      (COUNT START (1+ COUNT))
  159.      (NEW-LIST))
  160.     ((NULL LIST) (NREVERSE NEW-LIST))
  161.       (AND END (IF (= COUNT END) (RETURN (NREVERSE NEW-LIST))))
  162.       (PUSH (CAR LIST) NEW-LIST)))
  163.  
  164. #M
  165. (DEFUN SUBSTRING (STRING START &OPTIONAL END)
  166.     (IF (NOT (STRINGP STRING))
  167.     (FERROR NIL "The first argument to SUBSTRING, ~S, was not a string.")
  168.       (LEXPR-FUNCALL #'MAKE-STRING (SUBLIST (CDDR STRING) START END))))
  169.  
  170. #M
  171. (DEFUN TYO-STRING (STRING STREAM)
  172.     (IF (NOT (STRINGP STRING))
  173.     (FERROR NIL "The first argument to TYO-STRING, ~S, was not a string."
  174.         STRING))
  175.     (DO ((STRING (CDDR STRING) (CDR STRING)))
  176.     ((NULL STRING))
  177.       (TYO (CAR STRING) STREAM)))
  178.  
  179. #Q
  180. (DEFMACRO TYO-STRING (STRING STREAM)
  181.     `(PRINC ,STRING ,STREAM))
  182.  
  183.  
  184. ;;; The printer.  This code prints individual printable-box-objects, which look
  185. ;;; like this: (width row-list type height <anything else>).
  186. ;;; The printer assumes that the parameters for each printable-box-object are
  187. ;;; consistent with the contents of the box.  So, for example, it will break if
  188. ;;; you give it a printable-box-object that has inside it a printable-box-object
  189. ;;; that doesn't fit inside it.  Height is unnecessary for the printer.
  190.  
  191. (defvar *pbox-system-hacker* nil) ;controls error message printing.
  192. (DEFVAR *BOX-UNSELECTABLE-AREA-CHAR* #\SPACE)
  193. (DEFVAR *BOX-INPUTS-STRING* (STRING "->"))
  194. (DEFVAR *BOX-LEFT-SIDE-CHAR* #/|)
  195. (DEFVAR *BOX-RIGHT-SIDE-CHAR* #/|)
  196. (DEFVAR *BOX-LEFT-MARGIN-WIDTH* 1)
  197. (DEFVAR *BOX-RIGHT-MARGIN-WIDTH* 1)
  198. (DEFVAR *BOX-TOP-CHAR* #/-)
  199. (DEFVAR *BOX-BOTTOM-CHAR* #/-)
  200. (DEFVAR *BOX-LEFT-CORNER-CHAR* #/+)
  201. (DEFVAR *BOX-RIGHT-CORNER-CHAR* #/+)
  202. (DEFVAR *INTER-BOX-SPACING* 1)          ;vertical spacing between boxes
  203. (DEFVAR *BOX-IDENTIFIER-WIDTH* 4)      ;the number of a box on a page
  204. (DEFVAR *PAGE-WIDTH* 80.)          ;default if printing to file
  205. (DEFVAR *PAGE-HEIGHT* 70.)
  206. (DEFVAR *BOX-MAXIMUM-WIDTH* (- *PAGE-WIDTH* *BOX-IDENTIFIER-WIDTH*))
  207. ;;; the 1- is for the header
  208. (DEFVAR *BOX-MAXIMUM-HEIGHT* (1- (- *PAGE-HEIGHT* *INTER-BOX-SPACING*)))
  209. (DEFVAR *BOX-MINIMUM-WIDTH* 4)          ;includes sides
  210. (DEFVAR *BOX-MINIMUM-HEIGHT* 3)          ;includes top and bottom
  211. ;;; these settings give this for         +--+
  212. ;;; an empty box:                        |  |
  213. ;;;                                      +--+
  214.  
  215. ;;; BOX-WIDTH returns the width of a box.
  216. (DEFMACRO BOX-WIDTH (BOX)
  217.   `(IF (STRINGP ,BOX) (STRING-LENGTH ,BOX)
  218.      (CAR ,BOX)))
  219.  
  220. (DEFMACRO SET-BOX-WIDTH (BOX WIDTH)
  221.   `(SETF (CAR ,BOX) ,WIDTH))
  222.  
  223. (DEFMACRO BOX-HEIGHT (BOX)
  224.   `(IF (STRINGP ,BOX) 1
  225.      (CADDDR ,BOX)))
  226.  
  227. (DEFMACRO SET-BOX-HEIGHT (BOX HEIGHT)
  228.   `(SETF (CADDDR ,BOX) ,HEIGHT))
  229.  
  230. (DEFMACRO BOX-ROW-LIST (BOX)
  231.   `(CADR ,BOX))
  232.  
  233. (DEFMACRO SET-BOX-ROW-LIST (BOX NEW-ROW-LIST)
  234.   `(SETF (BOX-ROW-LIST ,BOX) ,NEW-ROW-LIST))
  235.  
  236. (DEFMACRO BOX-TYPE (BOX)
  237.   `(CADDR ,BOX))
  238.  
  239. (DEFMACRO SET-BOX-TYPE (BOX TYPE)
  240.   `(SETF (BOX-TYPE ,BOX) ,TYPE))
  241.  
  242. (DEFMACRO BOX-FIRST-ROW (BOX)
  243.   `(CAR (BOX-ROW-LIST ,BOX)))
  244.  
  245. (DEFMACRO REMOVE-FIRST-ROW (BOX)
  246.   `(SET-BOX-ROW-LIST ,BOX (CDR (BOX-ROW-LIST ,BOX))))
  247.  
  248. (DEFMACRO BOX-HAS-TOP? (BOX)
  249.   `(AND (NOT (NULL (BOX-ROW-LIST ,BOX)))
  250.     (EQ (BOX-FIRST-ROW ,BOX) 'TOP)))
  251.  
  252. (DEFMACRO SET-FIRST-BOX-ALREADY-PRINTED (BOXES)
  253.   `(LET ((BOX (CAR ,BOXES)))
  254.      (IF (NOT (STRINGP BOX)) (SETF (CDR BOX) NIL)
  255.        (SETF (CAR ,BOXES) (LIST (BOX-WIDTH BOX))))))
  256.  
  257. (DEFMACRO ALREADY-PRINTED-BOX? (BOX)
  258.   `(NULL (CDR ,BOX)))
  259.  
  260. (DEFMACRO BOX-ONLY-BOTTOM-TO-BE-PRINTED? (BOX)
  261.   `(AND (NULL (BOX-ROW-LIST ,BOX)) (= 1 (BOX-HEIGHT ,BOX))))
  262.  
  263. (DEFMACRO BOX-ONLY-VSPACE-TO-BE-PRINTED? (BOX)
  264.   `(AND (NULL (BOX-ROW-LIST ,BOX)) (> (BOX-HEIGHT ,BOX) 1)))
  265.  
  266. (DEFMACRO PRINT-EMPTY-LINE (BOX STREAM)
  267.   `(PROGN (TYO *BOX-LEFT-SIDE-CHAR* ,STREAM)
  268.       (TYO-N #\SPACE ,STREAM (- (BOX-WIDTH ,BOX) 2))
  269.       (TYO *BOX-RIGHT-SIDE-CHAR* ,STREAM)))
  270.  
  271. (DEFMACRO PRINT-BOX-BOTTOM (BOX STREAM)
  272.   `(PROGN (TYO *BOX-LEFT-CORNER-CHAR* ,STREAM)
  273.       (TYO-N *BOX-BOTTOM-CHAR* ,STREAM (- (BOX-WIDTH ,BOX) 2))
  274.       (TYO *BOX-RIGHT-CORNER-CHAR* ,STREAM)))
  275.  
  276. (DEFUN PRINT-BOX-TOP (BOX STREAM)
  277.   (TYO *BOX-LEFT-CORNER-CHAR* STREAM)
  278.   (TYO-STRING (BOX-TYPE BOX) STREAM)
  279.   (TYO-N *BOX-TOP-CHAR* STREAM
  280.      (- (BOX-WIDTH BOX) 2 (STRING-LENGTH (BOX-TYPE BOX))))
  281.   (TYO *BOX-RIGHT-CORNER-CHAR* STREAM))
  282.  
  283. ;;; TYO-N tyos N CHARs to STREAM.
  284. (DEFUN TYO-N (CHAR STREAM N)
  285.   (IF (MINUSP N) (FERROR NIL "The function TYO-N received the negative argument ~S for N.  The other
  286. arguments were ~S for CHAR and ~S for STREAM."
  287.              N CHAR STREAM))
  288.   (DO ((I N (1- I))) ((ZEROP I))
  289.      (TYO CHAR STREAM)))
  290.  
  291. ;;; Call this to print a box at top level.  PRINT-BOX-LINE and
  292. ;;; PRINT-FIRST-ROW-LINE necessarily print one line at a time, whereas this
  293. ;;; function prints an entire box, vertically as well as horizontally.
  294. (DEFUN PRINT-TOP-LEVEL-BOX (BOX STREAM)
  295.   (IF (STRINGP BOX) (PROGN (TYO-STRING BOX STREAM) (TERPRI STREAM))
  296.     (IF (OR (NULL BOX)              ;can't be nil
  297.         (NOT (NUMBERP (BOX-WIDTH BOX)))      ;has to have a width
  298.         (NULL (CDR BOX))          ;has to have a list of rows
  299.         ;;there has to be something in that list (at least 'TOP)
  300.         (NULL (BOX-ROW-LIST BOX)))
  301.     (FERROR NIL "The first argument to PRINT-TOP-LEVEL-BOX, ~S, is
  302. not a recognizable printable-box-object."
  303.         BOX))
  304.   (DO ((BOX-FINISHED? (PROG1 (PRINT-BOX-LINE BOX STREAM) (TERPRI STREAM))
  305.               (PROG1 (PRINT-BOX-LINE BOX STREAM) (TERPRI STREAM))))
  306.       (BOX-FINISHED?))))
  307.  
  308. ;;; PRINT-BOX-LINE returns NIL if it has not yet finished printing a box, else
  309. ;;; non-NIL.  Prints the first line of a box, including the first lines of any
  310. ;;; boxes inside it.  Causes inferior boxes to be suitably modified; i.e.,
  311. ;;; the printed line is removed from each inferior box.
  312. (DEFUN PRINT-BOX-LINE (BOX STREAM)
  313.   (COND ((STRINGP BOX) (TYO-STRING BOX STREAM) T)
  314.     ((ALREADY-PRINTED-BOX? BOX)
  315.      (TYO-N *BOX-UNSELECTABLE-AREA-CHAR* STREAM (BOX-WIDTH BOX)) T)
  316.     ((BOX-ONLY-BOTTOM-TO-BE-PRINTED? BOX)
  317.      (PRINT-BOX-BOTTOM BOX STREAM) T)
  318.     (T (IF (BOX-ONLY-VSPACE-TO-BE-PRINTED? BOX)
  319.            (PRINT-EMPTY-LINE BOX STREAM)
  320.          (PRINT-FIRST-ROW-LINE BOX STREAM))
  321.        ;; after printing a line, take note that there's one less to print,
  322.        ;; if the box will ever be seen again.
  323.        (SET-BOX-HEIGHT BOX (1- (BOX-HEIGHT BOX)))
  324.        NIL)))
  325.  
  326. ;;; PRINT-FIRST-ROW-LINE prints the first line of the first row of a box, and
  327. ;;; then replaces all fully printed boxes in it with already-printed-boxes.
  328. ;;; Then, if it has fully printed every box in the row, it removes the row from
  329. ;;; the box.
  330. (DEFUN PRINT-FIRST-ROW-LINE (BOX STREAM)
  331.   (IF (OR (NULL (CDR BOX)) (NULL (BOX-ROW-LIST BOX)))
  332.       (FERROR NIL "The printable-box-object ~S, which was the first argument
  333. to the function PRINT-FIRST-ROW-LINE, has an unrecognizable first row."
  334.           BOX))
  335.   (IF (BOX-HAS-TOP? BOX)
  336.       (PROGN (PRINT-BOX-TOP BOX STREAM) (REMOVE-FIRST-ROW BOX))
  337.       ;; if we weren't printing a boxtop, print a row.  Start with
  338.       ;; *BOX-LEFT-CHAR* and *BOX-LEFT-MARGIN-WIDTH*.
  339.       (LET ((CHARS-ALREADY-PRINTED (+ 1 *BOX-LEFT-MARGIN-WIDTH*)))
  340.     (TYO *BOX-LEFT-SIDE-CHAR* STREAM)
  341.     (TYO-N #\SPACE STREAM *BOX-LEFT-MARGIN-WIDTH*)
  342.     (DO ((WIDTH-TO-PRINT (- (BOX-WIDTH BOX) CHARS-ALREADY-PRINTED))
  343.          (BOXES (BOX-FIRST-ROW BOX) (CDR BOXES))
  344.          (ROW-FINISHED? T) (BOX-FINISHED?) (CURRENT-BOX))
  345.         ((NULL BOXES) (TYO-N #\SPACE STREAM
  346.                  (- WIDTH-TO-PRINT *BOX-RIGHT-MARGIN-WIDTH* 1))
  347.               (TYO-N #\SPACE STREAM *BOX-RIGHT-MARGIN-WIDTH*)
  348.               (TYO *BOX-RIGHT-SIDE-CHAR* STREAM)
  349.               (IF ROW-FINISHED? (REMOVE-FIRST-ROW BOX)))
  350.       (SETQ CURRENT-BOX (CAR BOXES)
  351.         WIDTH-TO-PRINT (- WIDTH-TO-PRINT (BOX-WIDTH CURRENT-BOX))
  352.         BOX-FINISHED?  (PRINT-BOX-LINE CURRENT-BOX STREAM)
  353.         ROW-FINISHED? (AND ROW-FINISHED? BOX-FINISHED?))
  354.       (IF BOX-FINISHED? (SET-FIRST-BOX-ALREADY-PRINTED BOXES))))))
  355.  
  356.  
  357.  
  358. ;;; The preprocessor.  The preprocessor is divided into two parts, the reader
  359. ;;; (which reads box files and conses up a printable-box-object, assuming no
  360. ;;; constraints), and the fitter, sometimes referred to in what follows as
  361. ;;; Procrustes, which operates on the printable-box-object and outputs a list
  362. ;;; of printable-box-objects, each of which is guaranteed to fit within the
  363. ;;; width of a page and be self-consistent.  The fitter has a list of tools,
  364. ;;; some of which are the exporter and the breaker (not implemented).
  365.  
  366. ;;; The reader.  The principal useful function in the reader is READ-BOX-FILE,
  367. ;;; which returns a list of self-consistent printable-box-objects.
  368.  
  369. ;;; No delimiter string can be a non-terminal subset of another delimiter
  370. ;;; string.  This is to avoid reading further than the end of a delimiter, which
  371. ;;; we don't want to do so we can call READ on the file whenever we expect that
  372. ;;; there will be a READable object next.
  373.  
  374.  
  375. (DEFCONST *BOX-FILE-START-BOX-STRING* #Q(MAKE-STRING BOXER:*STRT-BOX-CODE*)
  376.                                   #M(MAKE-STRING #/[))          
  377. (DEFCONST *BOX-FILE-END-BOX-STRING* #Q(MAKE-STRING BOXER:*STOP-BOX-CODE*)
  378.                                     #M(MAKE-STRING #/]))
  379. (DEFCONST *BOX-FILE-START-ROW-STRING* #Q(MAKE-STRING BOXER:*STRT-ROW-CODE*)
  380.                                       #M(MAKE-STRING #/{))
  381. (DEFCONST *BOX-FILE-END-ROW-STRING* #Q(MAKE-STRING BOXER:*STOP-ROW-CODE*)
  382.                                     #M(MAKE-STRING #/}))
  383. (DEFCONST *BOX-FILE-FONT-SPEC-STRING* #Q(MAKE-STRING #\ROMAN-IV)
  384.                       #M(MAKE-STRING #\RUBOUT #^X))
  385. (DEFCONST *BOX-FILE-QUOTING-STRING* #Q(MAKE-STRING #\EQUIVALENCE)
  386.                        #M(MAKE-STRING #^^))
  387. (DEFCONST *BOX-FILE-INPUTS-STRING* #Q(MAKE-STRING BOXER:*INPUTS-CODE*)
  388.                              #M(MAKE-STRING #^Y))
  389. (DEFCONST *BOX-FILE-LABEL-STRING* #Q(MAKE-STRING BOXER:*LABELLING-CODE*)
  390.                                   #M(MAKE-STRING #/:))
  391.  
  392. (DEFCONST *BOX-FILE-DELIMITERS*
  393.       (LIST *BOX-FILE-START-BOX-STRING* *BOX-FILE-END-BOX-STRING*
  394.         *BOX-FILE-START-ROW-STRING* *BOX-FILE-END-ROW-STRING*
  395.         *BOX-FILE-QUOTING-STRING* *BOX-FILE-FONT-SPEC-STRING*
  396.         *BOX-FILE-LABEL-STRING* *BOX-FILE-INPUTS-STRING*))
  397.  
  398. (DEFCONST *BOX-TYPE-PRETTY-NAMES*
  399.       (LIST (CONS ':DOIT-BOX (STRING "")) ;the calls to STRING are for
  400.         (CONS ':DATA-BOX (STRING "Data"))))      ;the benefit of MacLisp
  401.  
  402. (DEFCONST *THE-EMPTY-STRING* (STRING ""))
  403.  
  404. (DEFMACRO GET-PRETTY-TYPE-NAME (TYPE)
  405.   `(LET ((PRETTY-NAME (CDR (ASSQ ,TYPE *BOX-TYPE-PRETTY-NAMES*))))
  406.      (IF PRETTY-NAME PRETTY-NAME *THE-EMPTY-STRING*)))
  407.  
  408. (DEFMACRO PRINTABLE-BOX-OBJECT-WITHOUT-SIZE (ROWS TYPE)
  409.   `(LIST NIL                  ;width
  410.      (CONS 'TOP ,ROWS)          ;row-list
  411.      (GET-PRETTY-TYPE-NAME ,TYPE)      ;type
  412.      NIL                  ;height
  413.      NIL))                  ;last-export-pointer
  414.  
  415. ;;; get the thing after THING, jumping two at a time.  NIL if not found.
  416. (DEFUN GET-NEXT (THING LIST)
  417.   (COND ((NULL LIST) NIL)
  418.     ((EQUAL THING (CAR LIST))
  419.      (IF (NOT (NULL (CDR LIST))) (CADR LIST) NIL))
  420.     (T (GET-NEXT THING (CDDR LIST)))))
  421.  
  422. ;;; GREATEST returns the greatest result of the application of FUNCTION to each
  423. ;;; member of LIST.  > is used for the comparison.  0 is returned for the empty
  424. ;;; list.
  425. (DEFUN GREATEST (FUNCTION LIST)
  426.   (DO ((GREATEST-SO-FAR 0)
  427.        (LIST LIST (CDR LIST)) (THIS))
  428.       ((NULL LIST) GREATEST-SO-FAR)
  429.     (SETQ THIS (FUNCALL FUNCTION (CAR LIST)))      ;no DO* in MacLisp.
  430.     (IF (> THIS GREATEST-SO-FAR) (SETQ GREATEST-SO-FAR THIS))))
  431.  
  432. ;;; SUM returns the sum of the results of the application of FUNCTION to LIST.
  433. ;;; 0 is returned if the list is empty.  PLUS is used for addition.
  434. (DEFUN SUM (FUNCTION LIST)
  435.   (DO ((SUM-SO-FAR 0)
  436.        (LIST LIST (CDR LIST)))
  437.       ((NULL LIST) SUM-SO-FAR)
  438.     (SETQ SUM-SO-FAR (+ SUM-SO-FAR (FUNCALL FUNCTION (CAR LIST))))))
  439.  
  440. ;;; I hate Maclisp.
  441. #M
  442. (DEFUN RCHAR (STREAM EOF-OPTION)
  443.     (LET ((CHAR (TYI STREAM EOF-OPTION)))
  444.       (IF (= CHAR -1) NIL CHAR)))
  445.  
  446. #Q
  447. (DEFMACRO RCHAR (STREAM EOF-OPTION)
  448.     `(TYI ,STREAM ,EOF-OPTION))
  449.  
  450. #Q
  451. (DEFMACRO RLINE (STREAM)
  452.     `(READLINE ,STREAM))
  453.  
  454. #M
  455. (DEFMACRO RLINE (STREAM)
  456.     `(PROG1 (READLINE ,STREAM)
  457.         (IF (= (TYIPEEK NIL ,STREAM -1) #\LINEFEED)
  458.         (TYI ,STREAM))))
  459.  
  460. ;;; Return the character in STRING pointed to by POINTER, or if POINTER points 
  461. ;;; to the end of STRING, read in a char from STREAM and NCONC it to string, and
  462. ;;; return it.  If EOF is encountered, simply returns NIL.  Does not advance
  463. ;;; POINTER.
  464. (DEFUN GET-CHAR-STRING-OR-STREAM (STRING POINTER STREAM)
  465.   ;; if at end of string read a char from stream
  466.   (IF (POINTER-POINTS-TO-END? POINTER STRING)
  467.       (LET ((CHAR (RCHAR STREAM NIL)))
  468.     ;; if at EOF don't try to put at end of string.
  469.     (IF (NOT (NULL CHAR)) (STRING-NCONC STRING CHAR))
  470.     CHAR)
  471.     ;; otherwise just return the one we're at.
  472.     (CHAR-AT-POINTER POINTER STRING)))
  473.  
  474. (DEFMACRO GET-CHAR-STRING-OR-STREAM-AP (STRING POINTER STREAM)
  475.   `(PROG1 (GET-CHAR-STRING-OR-STREAM ,STRING ,POINTER ,STREAM)
  476.       (ADVANCE-POINTER ,POINTER)))
  477.  
  478. ;;; WITH-OPEN-FILE doesn't exist in MacLisp.
  479. #M
  480. (DEFMACRO WITH-OPEN-FILE ((STREAM FILE OPTIONS) &BODY BODY)
  481.     `(LET ((,STREAM NIL))
  482.        (UNWIND-PROTECT (PROGN (SETQ ,STREAM (OPEN ,FILE ,OPTIONS)) . ,BODY)
  483.                (CLOSE ,STREAM))))
  484.  
  485. ;;; READ-BOX-FILE returns a list of printable box objects, assuming no
  486. ;;; constraints on width or height.
  487. (DEFUN READ-BOX-FILE (FILE)
  488.   (WITH-OPEN-FILE (FILE-IN-STREAM FILE 'IN)
  489.     (READ-BOX-STREAM FILE-IN-STREAM)))
  490.  
  491. (DEFUN READ-BOX-STREAM (FILE-IN-STREAM)
  492.   (MAPC #'CALCULATE-AND-SET-BOX-SIZE        ;set the size parameters of
  493.     (PARSE-ROW-FROM-STREAM FILE-IN-STREAM)));each box
  494.  
  495. ;;; PARSE-ROW-FROM-STREAM returns a list of printable box objects with NIL in
  496. ;;; their size fields and those of all subboxes.  Comment lines are ignored.
  497. ;;; Returns 'END if there are no more rows in the box.
  498. ;;; Note that some boxes returned may be strings.
  499. (DEFUN PARSE-ROW-FROM-STREAM (STREAM)
  500.   (DO ((DELIMITER T) (STRING) (ROW))
  501.       ;; null delimiter means eof
  502.       ((OR (NULL DELIMITER) (STRING-EQUAL DELIMITER *BOX-FILE-END-ROW-STRING*))
  503.        (NREVERSE ROW))
  504.     ;; returns two values
  505.     (MULTIPLE-VALUE (DELIMITER STRING) (READ-STRING-UNTIL-DELIMITER-OR-EOF
  506.                      STREAM *BOX-FILE-DELIMITERS*))
  507.     (IF (STRING-EQUAL DELIMITER *BOX-FILE-END-BOX-STRING*)
  508.     (IF (OR (NOT (NULL ROW)) (NOT (STRING-EQUAL STRING DELIMITER)))
  509.         ;; if we got an end-box, and there was something before it, it's a
  510.         ;; bug.
  511.         (FERROR NIL "A box terminator was encountered in the middle ~
  512. of the row ~S.
  513. The string being read was ~S." (NREVERSE ROW) STRING)
  514.         (RETURN 'END)))      
  515.     (LET ((SUBSTRING (SUBSTRING STRING 0
  516.                 (- (STRING-LENGTH STRING)
  517.                    (IF (NULL DELIMITER) 0
  518.                        (STRING-LENGTH DELIMITER))))))
  519.       ;; if we immediately encountered a delimiter, don't keep the null string
  520.       (IF (NOT (STRING-EQUAL SUBSTRING *THE-EMPTY-STRING*))
  521.       (PUSH SUBSTRING ROW)))
  522.     (COND ((STRING-EQUAL DELIMITER *BOX-FILE-START-BOX-STRING*)
  523.        (PUSH (PARSE-BOX-FROM-STREAM STREAM) ROW))
  524.       ((STRING-EQUAL DELIMITER *BOX-FILE-QUOTING-STRING*)
  525.        (PUSH (STRING (TYI STREAM)) ROW))
  526.       ((STRING-EQUAL DELIMITER *BOX-FILE-FONT-SPEC-STRING*)
  527.        (TYI STREAM))
  528.       ((string-equal delimiter *box-file-label-string*)
  529.        (push *box-file-label-string* row))
  530.       ((STRING-EQUAL DELIMITER *BOX-FILE-INPUTS-STRING*)
  531.        (PUSH *BOX-INPUTS-STRING* ROW)))))
  532.  
  533. ;;; PARSE-BOX-FROM-STREAM returns a printable box object read from the stream
  534. ;;; STREAM.  Call it AFTER consuming the begin-box string.
  535. (DEFUN PARSE-BOX-FROM-STREAM (STREAM)
  536.   (LET ((BOX-DESCRIPTOR (READ STREAM)))
  537.     (IF (NOT (LISTP BOX-DESCRIPTOR))
  538.     (FERROR NIL "The box descriptor ~S is not a list.
  539. While reading a box from the stream ~S." BOX-DESCRIPTOR STREAM))
  540.     (DO ((TYPE (GET-NEXT ':TYPE BOX-DESCRIPTOR))
  541.      (ROW (PARSE-ROW-FROM-STREAM STREAM) (PARSE-ROW-FROM-STREAM STREAM))
  542.      (ROW-LIST))
  543.     ((EQ ROW 'END) (PRINTABLE-BOX-OBJECT-WITHOUT-SIZE
  544.              (NREVERSE ROW-LIST) TYPE))
  545.       (PUSH ROW ROW-LIST))))
  546.       
  547. ;;; Read a string until encountering a delimiter string, and MVR the delimiter
  548. ;;; string and the string.
  549. (DEFUN READ-STRING-UNTIL-DELIMITER-OR-EOF (STREAM DELIMITER-LIST)
  550.   (LET* ((STRING (MAKE-STRING)) (POINTER (START-POINTER STRING)))
  551.     (DO ((CHAR (GET-CHAR-STRING-OR-STREAM STRING POINTER STREAM)
  552.            (GET-CHAR-STRING-OR-STREAM STRING POINTER STREAM)))
  553.     ((NULL CHAR) (VALUES NIL STRING))
  554.       (LET ((MATCH? (MATCH-ANY STRING POINTER STREAM DELIMITER-LIST)))
  555.     (IF MATCH? (RETURN (VALUES MATCH? STRING))
  556.       (ADVANCE-POINTER POINTER))))))
  557.  
  558. ;;; try to match one of the strings in DELIMITER-LIST with the string and stream
  559. ;;; starting at POINTER.  Return NIL if lose, delimiter if won.
  560. (DEFUN MATCH-ANY (STRING POINTER STREAM DELIMITER-LIST)
  561.   (IF (NULL DELIMITER-LIST) NIL
  562.     (LET* ((SELF (CAR DELIMITER-LIST)) (SELF-POINTER (START-POINTER SELF))
  563.        (CHAR-POINTER POINTER))
  564.       (DO ((CHAR (GET-CHAR-STRING-OR-STREAM-AP STRING CHAR-POINTER STREAM)
  565.          (GET-CHAR-STRING-OR-STREAM-AP STRING CHAR-POINTER STREAM)))
  566.       (NIL)
  567.     ;;this will catch eof as well.
  568.     (IF (EQ (GET-CHAR-AND-ADVANCE-POINTER SELF-POINTER SELF) CHAR)
  569.         (IF (POINTER-POINTS-TO-END? SELF-POINTER SELF)
  570.         (RETURN SELF))
  571.       (RETURN (MATCH-ANY STRING POINTER STREAM (CDR DELIMITER-LIST))))))))
  572.  
  573. (DEFMACRO MAYBE-BOX? (BOX)
  574.   `(OR (STRINGP ,BOX)              ;a maybe-box is a string
  575.        (AND (LISTP ,BOX)          ;or, more likely, a list
  576.         (>= (LENGTH ,BOX) 4)      ;with at least 4 elements
  577.         (LISTP (CADR ,BOX))          ;row-list has to be a list
  578.         (STRINGP (CADDR ,BOX)))))      ;type has to be a string
  579.  
  580. ;;; CALCULATE-AND-SET-BOX-SIZE actually calculates and changes all the WIDTH
  581. ;;; and HEIGHT fields in the box and all its subboxes.
  582. (DEFUN CALCULATE-AND-SET-BOX-SIZE (BOX)
  583.   ;; validate the type somewhat.
  584.   (IF (NOT (MAYBE-BOX? BOX))
  585.       (FERROR NIL "The object ~S is not a recognizable box." BOX))
  586.   (CALCULATE-AND-SET-BOX-WIDTH BOX)
  587.   (CALCULATE-AND-SET-BOX-HEIGHT BOX))
  588.  
  589. ;;; Sets and returns BOX-WIDTH for this box and all subboxes.  Does no type
  590. ;;; check on BOX.
  591. (DEFUN CALCULATE-AND-SET-BOX-WIDTH (BOX)
  592.   (IF (STRINGP BOX) (STRING-LENGTH BOX)      ;don't set a string's width
  593.     (LET ((BOX-WIDTH
  594.         ;;the width of a box is the greatest of
  595.         (MAX
  596.           ;; the sum of the widths of its sides, margins, and widest row,
  597.           (+ *BOX-RIGHT-MARGIN-WIDTH* *BOX-LEFT-MARGIN-WIDTH* 2
  598.          (IF (NOT (BOX-HAS-TOP? BOX))
  599.              (FERROR NIL "~
  600. The printable-box-object ~S, which was the first argument to
  601. CALCULATE-AND-SET-BOX-WIDTH, has no top." BOX)
  602.            (GREATEST #'SET-AND-GET-ROW-WIDTH
  603.                  ;; don't consider the boxtop.
  604.                  (CDR (BOX-ROW-LIST BOX)))))
  605.           ;; the sum of the widths of its label and sides
  606.           (+ (STRING-LENGTH (BOX-TYPE BOX)) 2)
  607.           ;; and the minumum box width.
  608.           *BOX-MINIMUM-WIDTH*)))
  609.       (SET-BOX-WIDTH BOX BOX-WIDTH)
  610.       BOX-WIDTH)))
  611.  
  612. ;;; Set the width of each box in the row ROW (and all subboxes) and return the
  613. ;;; sum of their widths.
  614. (DEFUN SET-AND-GET-ROW-WIDTH (ROW)
  615.   ;; width of empty row being 0 follows from definition of SUM
  616.   (SUM #'CALCULATE-AND-SET-BOX-WIDTH ROW))
  617.  
  618. ;;; Sets and returns BOX-HEIGHT for this box and all subboxes.  Does no type
  619. ;;; check on BOX.
  620. (DEFUN CALCULATE-AND-SET-BOX-HEIGHT (BOX)
  621.   (IF (STRINGP BOX) 1              ;don't set a string's height
  622.     (LET ((BOX-HEIGHT (MAX *BOX-MINIMUM-HEIGHT*
  623.                (+ (IF (NOT (BOX-HAS-TOP? BOX))
  624.                   (FERROR NIL "~
  625. The printable-box-object ~S, which was the first argument to
  626. CALCULATE-AND-SET-BOX-HEIGHT, has no top." BOX)
  627.                 (SUM #'SET-AND-GET-ROW-HEIGHT
  628.                      ;; don't consider the boxtop.
  629.                      (CDR (BOX-ROW-LIST BOX))))
  630.                   2))))
  631.       (SET-BOX-HEIGHT BOX BOX-HEIGHT)
  632.       BOX-HEIGHT)))
  633.  
  634. ;;; Set the height of each box in ROW (and all subboxes) and return the
  635. ;;; greatest of their heights.
  636. (DEFUN SET-AND-GET-ROW-HEIGHT (ROW)
  637.   (IF (EQ ROW NIL) 1              ;the empty row is 1 tall.
  638.     (GREATEST #'CALCULATE-AND-SET-BOX-HEIGHT ROW)))
  639.  
  640.  
  641.  
  642. ;;; The fitter, or Procrustes.
  643.  
  644. ;;; The fitter has a list of functions to call on a box which is too large to
  645. ;;; be printed.  It calls them sequentially until one works.  Each fitting
  646. ;;; function is expected to accept a list whose first member is the
  647. ;;; printable-box-object to be fitted; the rest is the rest of the boxes to be
  648. ;;; printed.  This is so the exporter can put the boxes it exports somewhere
  649. ;;; (like immediately after the box it exports them from).  Each fitting
  650. ;;; function is also expected to accept as second and third arguments the
  651. ;;; maximum width and height of a box.  If a fitting function decides that it
  652. ;;; cannot cure the problem, it returns NIL.  All fitting functions work by
  653. ;;; mutating the list they have been handed.
  654.  
  655. (DEFVAR *BOX-FITTING-FUNCTIONS* NIL)
  656.  
  657. ;;; returns the box-list, suitably modified.
  658. (DEFUN FIT (BOX-LIST &OPTIONAL (FITTERS *BOX-FITTING-FUNCTIONS*))
  659.   (DO ((BOXES BOX-LIST (CDR BOXES)) (BOX))
  660.       ((NULL BOXES) BOX-LIST)
  661.     (SETQ BOX (CAR BOXES))
  662.     (IF (OR (> (BOX-WIDTH BOX) *BOX-MAXIMUM-WIDTH*)
  663.         (> (BOX-HEIGHT BOX) *BOX-MAXIMUM-HEIGHT*))
  664.     (DO ((FITTING-FUNCTIONS FITTERS (CDR FITTING-FUNCTIONS)))
  665.         ((NULL FITTING-FUNCTIONS)
  666.          (NOTIFY-USER-ABOUT-BOX-FITTING-LOSSAGE box))
  667.       (IF (FUNCALL (CAR FITTING-FUNCTIONS) BOXES *BOX-MAXIMUM-WIDTH*
  668.                *BOX-MAXIMUM-HEIGHT*)
  669.           (RETURN NIL))))))
  670.  
  671. (DEFUN NOTIFY-USER-ABOUT-BOX-FITTING-LOSSAGE (BOX)
  672.   (IF (NULL *PBOX-SYSTEM-HACKER*)
  673.       (FORMAT T "~%A box of width ~D and height ~D is too big to fit on the page."
  674.           (BOX-WIDTH BOX)
  675.           (box-height box))
  676.     (LET ((PRINLEVEL 3)
  677.       (PRINLENGTH 3))
  678.       (FORMAT T
  679.           "The printable-box-object ~S,
  680. with width ~D and height ~D, cannot be mutated to fit within the
  681. width (~D) and height (~D) of the page."
  682.           BOX (BOX-WIDTH BOX) (BOX-HEIGHT BOX) *BOX-MAXIMUM-WIDTH*
  683.           *BOX-MAXIMUM-HEIGHT*)
  684.       (IF (NOT (Y-OR-N-P (FORMAT NIL "~%Continue anyway? ")))
  685.       (BREAK "-- you lose")))))
  686.     
  687.  
  688. ;;; The exporter is the only fitting function implemented so far.  The exporter
  689. ;;; grovels over the first box in the box-list it is handed, first adjusting
  690. ;;; its width to fit, then adjusting its height.  Because these are done
  691. ;;; sequentially, the resulting configuration may actually be less wide than it
  692. ;;; has to be; that is, if a box is exported because it is too tall, it may
  693. ;;; happen that it is also on the widest row, so it may make the box thinner
  694. ;;; than need be.  A second pass should cure this.
  695.  
  696. ;;; If this is a box, the exporter should only copy this object, never use it!
  697. (DEFVAR *EXPORT-BOX-MODEL* (STRING "|pg 00,#00|"))
  698.  
  699. (DEFVAR *DO-EXPORTS-FOR-WIDTH* T)
  700. (DEFVAR *DO-EXPORTS-FOR-HEIGHT* T)
  701. (DEFVAR *BOX-MINIMUM-EXPORT-HEIGHT* 4)
  702.  
  703. (DEFMACRO EXPORT-PART (BOX)
  704.   `(NTHCDR 5 ,BOX))
  705.  
  706. ;;; Every printable-box-object has a part, the last-export-pointer, which
  707. ;;; comes after the height. Looks like:
  708. ;;; (WIDTH ROW-LIST TYPE HEIGHT LAST-EXPORT-POINTER . EXPORT-PART)
  709. ;;; While the export part is a backpointer from a box that has been exported to
  710. ;;; the place from which it was exported, the last-export-pointer is a pointer
  711. ;;; from a top-level box from which something has been exported to where the
  712. ;;; next thing should be exported to.  It is meant to aid in the ordering of
  713. ;;; export boxes.  If it is null, the next export should go immediately after
  714. ;;; this box; otherwise it's a pointer to the list that the last exported box
  715. ;;; started and the next export box should go after that box, and the
  716. ;;; last-export-pointer should be updated.  Since exports all happen in one
  717. ;;; pass, the result will be okay, even though the last-export-pointer of a box
  718. ;;; will no longer be good after things have been exported from one of its
  719. ;;; exports.
  720.  
  721. (DEFMACRO LAST-EXPORT-POINTER (BOX)
  722.   `(CAR (CDDDDR ,BOX)))
  723.  
  724. (DEFMACRO SET-LAST-EXPORT-POINTER (BOX THING)
  725.   `(SETF (LAST-EXPORT-POINTER ,BOX) ,THING))
  726.  
  727. (DEFUN EXPORT-SUBBOXES-IF-NECESSARY (BOX-LIST MAX-WIDTH MAX-HEIGHT)
  728.   (IF (NULL BOX-LIST) (FERROR NIL
  729. "The function EXPORT-SUBBOXES-IF-NECESSARY was given an empty box-list."))
  730.   (LET ((BOX (CAR BOX-LIST)))
  731.     ;; if the maximum width is less than the box-top width, can't fix.
  732.     (IF (< MAX-WIDTH (+ 2 (STRING-LENGTH (BOX-TYPE BOX)))) NIL
  733.       (AND (IF (> (BOX-WIDTH BOX) MAX-WIDTH)
  734.            ;; this'll return NIL if it tries and loses
  735.            (IF (NOT *DO-EXPORTS-FOR-WIDTH*) T
  736.          (EXPORT-FOR-WIDTH BOX-LIST MAX-WIDTH))
  737.          ;;T if there's no problem, because then it's solved.
  738.          T)
  739.        (IF (> (BOX-HEIGHT BOX) MAX-HEIGHT)
  740.            ;; this'll return NIL if it tries and loses
  741.            (IF (NOT *DO-EXPORTS-FOR-HEIGHT*) T
  742.          (EXPORT-FOR-HEIGHT BOX-LIST MAX-HEIGHT))
  743.          T)))))
  744.  
  745. (PUSH  #'EXPORT-SUBBOXES-IF-NECESSARY *BOX-FITTING-FUNCTIONS*)
  746.  
  747. (DEFMACRO EXPORTABLE? (BOX)
  748.   `(AND (NOT (STRINGP ,BOX))
  749.     (>= (BOX-HEIGHT ,BOX) *BOX-MINIMUM-EXPORT-HEIGHT*)))
  750.  
  751. ;;; EXPORT-FOR-WIDTH attempts to export from the widest row the smallest single
  752. ;;; box that will cure the problem.  If no single box can be exported to cure
  753. ;;; the problem, the widest box on the row will be removed and the exporter
  754. ;;; will be called again.  [Note: in the plural case, this won't really find
  755. ;;; the best combination; it's just simple.  That is, there may be a pair of
  756. ;;; boxes that exactly cure the problem that don't include the largest box.]
  757. (DEFUN EXPORT-FOR-WIDTH (BOX-LIST MAX-WIDTH)
  758.   (LET* ((BOX (CAR BOX-LIST))
  759.      (WIDTH-OVER-MAXIMUM (- (BOX-WIDTH BOX) MAX-WIDTH))
  760.      (EXPORTABLE-WIDTH (+ (BOX-WIDTH *EXPORT-BOX-MODEL*)
  761.                   WIDTH-OVER-MAXIMUM)))
  762.     (IF (<= WIDTH-OVER-MAXIMUM 0) T
  763.       (LET ((BEST-BOX-LIST
  764.           (BOX-WITH-WIDTH-CLOSEST-TO
  765.         EXPORTABLE-WIDTH (WIDEST-ROW-NOT-TOP BOX))))
  766.     (IF BEST-BOX-LIST
  767.         (LET ((BEST-WIDTH (BOX-WIDTH (CAR BEST-BOX-LIST))))
  768.           (IF (>= BEST-WIDTH (BOX-WIDTH *EXPORT-BOX-MODEL*))
  769.           (PROGN (EXPORT-IT BEST-BOX-LIST BOX-LIST)
  770.              (CALCULATE-AND-SET-BOX-SIZE BOX)
  771.              (EXPORT-FOR-WIDTH BOX-LIST MAX-WIDTH))
  772.         NIL)))))))
  773.  
  774. ;;; recursively walk the subboxes of a box and find the box with width closest
  775. ;;; to, but greater than or equal to, the width given, or if there are none
  776. ;;; greater than or equal to, the widest.  Strings are never considered.  NIL
  777. ;;; if no subboxes.  Returns the list that the box starts.
  778. (DEFUN BOX-WITH-WIDTH-CLOSEST-TO (WIDTH BOX-LIST)
  779.   (DO ((BOX-LIST BOX-LIST (CDR BOX-LIST))
  780.        (CURRENT-WIDTH) (BEST-WIDTH 0) (BEST-BOX-LIST))
  781.       ((NULL BOX-LIST) BEST-BOX-LIST)
  782.     (IF (EXPORTABLE? (CAR BOX-LIST))
  783.     (PROGN (SETQ CURRENT-WIDTH (BOX-WIDTH (CAR BOX-LIST)))
  784.            ;; if the current box is better than the best so far
  785.            (IF (SORT-OF-CLOSER? CURRENT-WIDTH BEST-WIDTH WIDTH)
  786.            ;; make it the best box
  787.            (SETQ BEST-WIDTH CURRENT-WIDTH BEST-BOX-LIST BOX-LIST))
  788.            (LET ((BEST-SUBBOX-LIST
  789.                (BOX-WITH-WIDTH-CLOSEST-TO
  790.              WIDTH (WIDEST-ROW-NOT-TOP (CAR BOX-LIST)))))
  791.          ;; if there is a best subbox
  792.          (AND BEST-SUBBOX-LIST
  793.               ;;and it's better than the best so far
  794.               (IF (SORT-OF-CLOSER? (BOX-WIDTH (CAR BEST-SUBBOX-LIST))
  795.                        BEST-WIDTH WIDTH)
  796.               ;; then it's the best box
  797.               (SETQ BEST-BOX-LIST BEST-SUBBOX-LIST
  798.                 BEST-WIDTH
  799.                 (BOX-WIDTH (CAR BEST-SUBBOX-LIST))))))))))
  800.  
  801. ;;; if CURRENT-QUANTITY is closer to QUANTITY than BEST-QUANTITY is, return t,
  802. ;;; else nil; but use a strange definition for closer.  If both
  803. ;;; CURRENT-QUANTITY and BEST-QUANTITY are smaller or greater than QUANTITY,
  804. ;;; then the one actually closer is correct; but if one is over and one under,
  805. ;;; the one over is preferred.
  806. (DEFUN SORT-OF-CLOSER? (CURRENT-QUANTITY BEST-QUANTITY QUANTITY)
  807.   (IF (>= BEST-QUANTITY QUANTITY)
  808.       ;; if best-quantity over the desired, then current-quantity
  809.       ;; has to be between it and desired to be better.
  810.       (>= BEST-QUANTITY CURRENT-QUANTITY QUANTITY)
  811.     ;; if best-quantity less than desired, current-quantity need
  812.     ;; only be bigger to be better.
  813.     (> CURRENT-QUANTITY BEST-QUANTITY)))
  814.  
  815. ;;; returns NIL if no rows beside top
  816. (DEFUN WIDEST-ROW-NOT-TOP (BOX)
  817.   (LET ((ROW-LIST (IF (BOX-HAS-TOP? BOX) (CDR (BOX-ROW-LIST BOX))
  818.             (BOX-ROW-LIST BOX))))
  819.     (DO ((ROW-LIST ROW-LIST (CDR ROW-LIST))
  820.      (CURRENT-ROW) (CURRENT-WIDTH) (WIDEST-ROW) (WIDEST-WIDTH 0))
  821.     ((NULL ROW-LIST) WIDEST-ROW)
  822.       (SETQ CURRENT-ROW (CAR ROW-LIST)
  823.         CURRENT-WIDTH (ROW-WIDTH CURRENT-ROW))
  824.       (IF (> CURRENT-WIDTH WIDEST-WIDTH)
  825.       (SETQ WIDEST-ROW CURRENT-ROW WIDEST-WIDTH CURRENT-WIDTH)))))
  826.  
  827. (DEFUN ROW-WIDTH (ROW)
  828.   (IF (NOT (OR (LISTP ROW) (NULL ROW))) (FERROR NIL
  829. "The function ROW-WIDTH was given the value ~S, which should have
  830. been a list, for ROW." ROW))
  831.   (DO ((WIDTH 0 (+ WIDTH (BOX-WIDTH (CAR BOX-LIST))))
  832.        (BOX-LIST ROW (CDR BOX-LIST)))
  833.       ((NULL BOX-LIST) WIDTH)))
  834.  
  835. #M
  836. (DEFUN COPYTREE (TREE)
  837.     (IF (OR (STRINGP TREE)          ;for MacLisp "strings"
  838.         (NOT (LISTP TREE))) TREE
  839.       (MAPCAR #'COPYTREE TREE)))
  840.  
  841. ;;;; symbol conflict
  842. ;(EVAL-WHEN (LOAD COMPILE)
  843. ;  (SHADOW 'EXPORT)
  844. ;  )   ;; this didn't work, I'm just going to change the name of the
  845. ;      ;; function
  846.  
  847. ;;; actually replace the given box with an export box, add a pointer from the
  848. ;;; export box to the pointer, put the export box in the right place, and reset
  849. ;;; the last-export-pointer of the box.
  850. (DEFUN EXPORT-IT (LIST-THAT-BOX-STARTS BOX-LIST)
  851.   (LET ((BOX (CAR LIST-THAT-BOX-STARTS)))
  852.     ;; remember that before printing this PAGIFY-BOX-LIST must be run on the
  853.     ;; list to replace the model with the real thing.
  854.     (SETF (CAR LIST-THAT-BOX-STARTS) *EXPORT-BOX-MODEL*)
  855.     ;;; if the box has no last-export-pointer yet, give it one.
  856.     (IF (NULL (LAST-EXPORT-POINTER BOX))
  857.     (SET-LAST-EXPORT-POINTER BOX BOX-LIST))
  858.     ;; the exported box goes in the cdr of the last-export-pointer, i.e., after
  859.     ;; the last box expoted from this box.
  860.     (LET ((NEW-EXPORT-POINTER (CONS BOX (CDR (LAST-EXPORT-POINTER BOX)))))
  861.       (SETF (CDR (LAST-EXPORT-POINTER BOX)) NEW-EXPORT-POINTER)
  862.       ;; then the last-export-pointer gets reset to point to the new last box
  863.       ;; exported.
  864.       (SET-LAST-EXPORT-POINTER BOX NEW-EXPORT-POINTER))
  865.     ;; finally, set the back-pointer from the exported box to the export
  866.     ;; pointer.
  867.     (SETF (EXPORT-PART BOX) LIST-THAT-BOX-STARTS))) 
  868.  
  869. (DEFUN EXPORT-FOR-HEIGHT (BOX-LIST MAX-HEIGHT)
  870.   (LET* ((BOX (CAR BOX-LIST))
  871.      (HEIGHT-OVER-MAXIMUM (- (BOX-HEIGHT BOX) MAX-HEIGHT)))
  872.     (IF (<= HEIGHT-OVER-MAXIMUM 0) T
  873.       (MULTIPLE-VALUE-BIND (BEST-BOX-LIST BEST-SAVING)
  874.       (BOX-WITH-HEIGHT-SAVING-CLOSEST-TO
  875.         HEIGHT-OVER-MAXIMUM (CDR (BOX-ROW-LIST BOX)))
  876.     (IF BEST-BOX-LIST
  877.         (IF (>= BEST-SAVING HEIGHT-OVER-MAXIMUM)
  878.         (PROGN (EXPORT-IT BEST-BOX-LIST BOX-LIST)
  879.                (CALCULATE-AND-SET-BOX-SIZE BOX))
  880.           (IF (> (BOX-HEIGHT (CAR BEST-BOX-LIST))
  881.              (BOX-HEIGHT *EXPORT-BOX-MODEL*))
  882.           (PROGN (EXPORT-IT BEST-BOX-LIST BOX-LIST)
  883.              (CALCULATE-AND-SET-BOX-SIZE BOX)
  884.              (EXPORT-FOR-HEIGHT BOX-LIST MAX-HEIGHT)))))))))
  885.  
  886. ;;; recursively determine the box or subbox in this row-list whose exportation
  887. ;;; would result in a reduction in height (of the box) closest to the quantity
  888. ;;; HEIGHT.  MVRs the list the box starts and amount saved or NIL if none.
  889. (DEFUN BOX-WITH-HEIGHT-SAVING-CLOSEST-TO (HEIGHT ROW-LIST)
  890.   (DO ((ROW-LIST ROW-LIST (CDR ROW-LIST))
  891.        (CURRENT-SAVING) (CURRENT-SUBLIST) (BEST-SAVING 0) (BEST-SUBLIST))
  892.       ((NULL ROW-LIST) (VALUES BEST-SUBLIST BEST-SAVING))
  893.     (MULTIPLE-VALUE (CURRENT-SUBLIST CURRENT-SAVING)
  894.       (HEIGHT-SAVING-BOX (CAR ROW-LIST)))
  895.     (IF CURRENT-SUBLIST
  896.     (PROGN (IF (SORT-OF-CLOSER? CURRENT-SAVING BEST-SAVING HEIGHT)
  897.            (SETQ BEST-SAVING CURRENT-SAVING
  898.              BEST-SUBLIST CURRENT-SUBLIST))
  899.            (MULTIPLE-VALUE-BIND (BEST-SUBBOX-SUBLIST BEST-SUBBOX-SAVING)
  900.            (BOX-WITH-HEIGHT-SAVING-CLOSEST-TO
  901.              HEIGHT (CDR (BOX-ROW-LIST (CAR CURRENT-SUBLIST))))
  902.          (AND BEST-SUBBOX-SUBLIST
  903.               (IF (SORT-OF-CLOSER? BEST-SUBBOX-SAVING BEST-SAVING
  904.                        HEIGHT)
  905.               (SETQ BEST-SAVING BEST-SUBBOX-SAVING
  906.                 BEST-SUBLIST BEST-SUBBOX-SUBLIST))))))))
  907.  
  908. ;;; find the box whose removal would decrease this row's height and return the
  909. ;;; list it starts and the amount that would be saved.
  910. (DEFUN HEIGHT-SAVING-BOX (ROW)
  911.   (DO ((BOX-LIST ROW (CDR BOX-LIST))
  912.        (TALLEST-SUBLIST) (TALLEST-HEIGHT 0) (NEXT-TALLEST-SUBLIST)
  913.        (NEXT-TALLEST-HEIGHT 0) (CURRENT-HEIGHT))
  914.       ((NULL BOX-LIST)
  915.        ;; only one box ever decreases the height of a row, so check here.
  916.        (IF (AND TALLEST-SUBLIST (EXPORTABLE? (CAR TALLEST-SUBLIST)))
  917.        (VALUES TALLEST-SUBLIST
  918.            (- TALLEST-HEIGHT NEXT-TALLEST-HEIGHT))
  919.      NIL))
  920.     (SETQ CURRENT-HEIGHT (BOX-HEIGHT (CAR BOX-LIST)))
  921.     (IF (> CURRENT-HEIGHT TALLEST-HEIGHT)
  922.     (PSETQ TALLEST-SUBLIST BOX-LIST TALLEST-HEIGHT CURRENT-HEIGHT
  923.            NEXT-TALLEST-SUBLIST TALLEST-SUBLIST
  924.            NEXT-TALLEST-HEIGHT TALLEST-HEIGHT)
  925.       (IF (> CURRENT-HEIGHT NEXT-TALLEST-HEIGHT)
  926.       (SETQ NEXT-TALLEST-SUBLIST BOX-LIST
  927.         NEXT-TALLEST-HEIGHT CURRENT-HEIGHT)))))
  928.  
  929. ;;; The page generator.
  930. #M
  931. (DEFVAR STANDARD-OUTPUT T)
  932. (DEFCONST *PAGE-END-STRING* (STRING #Q (FORMAT NIL "~|")
  933.                     #M #^L))
  934.  
  935. ;;; print a box list to a stream.  If no stream, standard-output.
  936. (DEFUN PRINT-BOX-LIST (BOX-LIST WHERE)
  937.   (DO ((BOX-LIST BOX-LIST (CDR BOX-LIST))
  938.        (BOX-NUMBER 1))
  939.       ((NULL BOX-LIST))
  940.     (IF (STRINGP (CAR BOX-LIST))
  941.     (PROGN (TYO-STRING (CAR BOX-LIST) WHERE)
  942.            (TERPRI WHERE)
  943.            (IF (STRING-EQUAL (CAR BOX-LIST) *PAGE-END-STRING*)
  944.            (SETQ BOX-NUMBER 1)))
  945.       (DO ((BOX-FINISHED?
  946.          ;; print the box number, with a ". " after, enough padding
  947.          ;; before to have a total of *BOX-INDENTIFIER-WIDTH*
  948.          ;; characters.
  949.          (PROG2 (TYO-STRING
  950.               (STRING (FORMAT NIL "~VD. "
  951.                       (- *BOX-IDENTIFIER-WIDTH* 2)
  952.                       BOX-NUMBER))
  953.               WHERE)
  954.             ;; and a line of the box
  955.             (PRINT-BOX-LINE (CAR BOX-LIST) WHERE)
  956.             ;; then a CR
  957.             (TERPRI WHERE))
  958.          (PROG2 (TYO-N #\SPACE WHERE *BOX-IDENTIFIER-WIDTH*)
  959.             (PRINT-BOX-LINE (CAR BOX-LIST) WHERE)
  960.             (TERPRI WHERE))))
  961.       (BOX-FINISHED?))
  962.       (SETQ BOX-NUMBER (1+ BOX-NUMBER)))))
  963.                         
  964. ;;; keeps the first cons the same.
  965. (DEFUN PUSH+ (THING CONS)
  966.   (IF (OR (NOT (LISTP CONS)) (NULL CONS))
  967.       (FERROR NIL
  968.           "The function PUSH+ was given a second argument of ~S, which was
  969. of the wrong type.  The function expected a cons." CONS))
  970.   (LET ((NEWCDR (NCONS (CAR CONS))))
  971.     (RPLACD NEWCDR (CDR CONS))
  972.     (RPLACA CONS THING)
  973.     (RPLACD CONS NEWCDR)))
  974.  
  975. ;;; Being for the benefit of Mr. Maclisp FORMAT.
  976. #M (DEFUN UNSTRINGIFY (STRING)
  977.      (IF (NOT (STRINGP STRING)) (FERROR NIL "The argument to UNSTRINGIFY, ~S,~
  978. was not a string." STRING)
  979.        (IMPLODE (CDDR STRING))))
  980.  
  981. (DEFUN PAGIFY-BOX-LIST (BOX-LIST PAGE-WIDTH PAGE-HEIGHT LEFT-HEADER RIGHT-HEADER)
  982.   ;; make sure both left and right headers are same length so FORMAT wins
  983.   (COND ((> (STRING-LENGTH LEFT-HEADER) (STRING-LENGTH RIGHT-HEADER))
  984.      (SETQ RIGHT-HEADER
  985.            (STRING (FORMAT NIL "~VX~A" (- (STRING-LENGTH LEFT-HEADER)
  986.                           (STRING-LENGTH RIGHT-HEADER))
  987.                    #M (UNSTRINGIFY RIGHT-HEADER)
  988.                    #Q RIGHT-HEADER))))
  989.     ((> (STRING-LENGTH RIGHT-HEADER) (STRING-LENGTH LEFT-HEADER))
  990.      (SETQ LEFT-HEADER
  991.            (STRING (FORMAT NIL "~A~VX" #M (UNSTRINGIFY LEFT-HEADER)
  992.                    #Q LEFT-HEADER
  993.                    (- (STRING-LENGTH RIGHT-HEADER)
  994.                   (STRING-LENGTH LEFT-HEADER)))))))
  995.   (DO ((BOXES BOX-LIST) (PAGE 1 (1+ PAGE)))
  996.       ((NULL BOXES) BOX-LIST)
  997.     ;; insert the header and an empty line
  998.     (PUSH+ (STRING (FORMAT NIL "~V<~A~;-~D-~;~A~>" PAGE-WIDTH
  999.                #M (UNSTRINGIFY LEFT-HEADER) #Q LEFT-HEADER
  1000.                PAGE
  1001.                #M (UNSTRINGIFY RIGHT-HEADER) #Q RIGHT-HEADER))
  1002.        BOXES)
  1003.     ;; now cdr down the list of boxes until no more will fit on the page,
  1004.     ;; inserting vertical spacing between them.
  1005.     (DO ((SPACING *THE-EMPTY-STRING*) (BOX)
  1006.      (LINES-LEFT (1- PAGE-HEIGHT)
  1007.              (- LINES-LEFT
  1008.             (+ *INTER-BOX-SPACING* (BOX-HEIGHT BOX))))
  1009.      (BOX-NUMBER 1 (1+ BOX-NUMBER))
  1010.      (BOXES-MAYBE-ON-THIS-PAGE (CDR BOXES) (CDR BOXES-MAYBE-ON-THIS-PAGE)))
  1011.     ((OR (NULL BOXES-MAYBE-ON-THIS-PAGE)
  1012.          (> (+ *INTER-BOX-SPACING*
  1013.            (BOX-HEIGHT (CAR BOXES-MAYBE-ON-THIS-PAGE)))
  1014.         LINES-LEFT))
  1015.      (SETQ BOXES BOXES-MAYBE-ON-THIS-PAGE))
  1016.       (SETQ BOX (CAR BOXES-MAYBE-ON-THIS-PAGE))
  1017.       ;; insert the spacing
  1018.       (DO ((I *INTER-BOX-SPACING* (1- I))) ((ZEROP I))
  1019.     (PUSH+ SPACING BOXES-MAYBE-ON-THIS-PAGE))
  1020.       ;; jump over it
  1021.       (SETQ BOXES-MAYBE-ON-THIS-PAGE
  1022.         (NTHCDR *INTER-BOX-SPACING* BOXES-MAYBE-ON-THIS-PAGE))
  1023.       ;; update the export pointer (if there) to point to this box's location.
  1024.       (IF (AND (NOT (STRINGP BOX)) (EXPORTED? BOX))
  1025.       (MAKE-EXPORT-POINTER-POINT-TO-BOX BOX PAGE BOX-NUMBER)))
  1026.     ;; now insert a page-break unless at end of the list (and thus file)
  1027.     (IF BOXES (PROGN (PUSH+ *PAGE-END-STRING* BOXES)
  1028.              (SETQ BOXES (CDR BOXES))))))
  1029.  
  1030. (DEFUN USERNAME-STRING ()
  1031.   (STRING #Q FS:USER-PERSONAL-NAME-FIRST-NAME-FIRST
  1032.       #M (STATUS UNAME)))
  1033.  
  1034. (DEFUN FILENAME-STRING (STRING)
  1035.   (STRING (WITH-OPEN-FILE (PATHNAME STRING)
  1036.         #M(NAMESTRING (TRUENAME PATHNAME))
  1037.         #Q(FUNCALL (FUNCALL PATHNAME ':TRUENAME) ':STRING-FOR-PRINTING))))
  1038.  
  1039. (DEFUN PRINT-BOXES-FROM-FILE (FROM-FILE &OPTIONAL TO-FILE)
  1040.   (WITH-OPEN-FILE (FROM-STREAM FROM-FILE '(IN ASCII))
  1041.     (READLINE FROM-STREAM)            ;flush the comment
  1042.     (COND ((NOT (NULL TO-FILE))
  1043.        (WITH-OPEN-FILE (TO-STREAM TO-FILE '(OUT ASCII))
  1044.          (PRINT-BOXES-FROM-STREAM-TO-STREAM FROM-STREAM TO-STREAM
  1045.                         *PAGE-WIDTH* *PAGE-HEIGHT*
  1046.                         (USERNAME-STRING)
  1047.                         (FILENAME-STRING FROM-FILE))))
  1048.       (T
  1049.        (PRINT-BOXES-FROM-STREAM-TO-STREAM FROM-STREAM STANDARD-OUTPUT
  1050.                           *PAGE-WIDTH* *PAGE-HEIGHT*
  1051.                           (USERNAME-STRING)
  1052.                           (FILENAME-STRING FROM-FILE))))))
  1053.  
  1054. (DEFUN PRINT-BOXES-FROM-STREAM-TO-STREAM
  1055.        (FROM-STREAM TO-STREAM PAGE-WIDTH PAGE-HEIGHT UNAME FILENAME)
  1056.   (PRINT-BOX-LIST (PAGIFY-BOX-LIST (FIT (READ-BOX-STREAM FROM-STREAM))
  1057.                    PAGE-WIDTH PAGE-HEIGHT
  1058.                    UNAME FILENAME)
  1059.           TO-STREAM))
  1060.  
  1061. (DEFUN HARDCOPY-BOXER-FILE (PATHNAME)
  1062.   (WITH-OPEN-FILE (STREAM PATHNAME ':READ)
  1063.     (SI:HARDCOPY-FROM-STREAM STREAM SI:*DEFAULT-HARDCOPY-DEVICE* ':PAGE-HEADINGS NIL)))
  1064.  
  1065. #Q
  1066. (DEFUN HARDCOPY-BOX (BOX)
  1067.     (LET ((TEMP-PATHNAME-1
  1068.         (SEND (FS:USER-HOMEDIR) ':NEW-PATHNAME ':NAME "PBOX" ':TYPE "TEMP1"))
  1069.       (TEMP-PATHNAME-2
  1070.         (SEND (FS:USER-HOMEDIR) ':NEW-PATHNAME ':NAME "PBOX" ':TYPE "TEMP2")))
  1071.       (BOXER:OLD-WRITE-BOX-INTO-FILE BOX TEMP-PATHNAME-1)
  1072.       (PRINT-BOXES-FROM-FILE TEMP-PATHNAME-1 TEMP-PATHNAME-2)
  1073.       (HARDCOPY-BOXER-FILE TEMP-PATHNAME-2)
  1074.       (FS:DELETEF TEMP-PATHNAME-1)
  1075.       (FS:DELETEF TEMP-PATHNAME-2)
  1076.       ))
  1077.  
  1078.  
  1079. (DEFUN EXPORTED? (BOX)
  1080.   (IF (OR (STRINGP BOX) (NOT (MAYBE-BOX? BOX)))
  1081.       (FERROR NIL "The function EXPORTED? received as argument the object ~S, ~
  1082. which is~% not a box." BOX))
  1083.   (EXPORT-PART BOX))
  1084.  
  1085. (DEFUN MAKE-EXPORT-POINTER-POINT-TO-BOX (BOX PAGE BOX-NUMBER)
  1086.   (SETF (CAR (EXPORT-PART BOX))
  1087.     (STRING (FORMAT NIL "|pg ~2D,#~2D|" PAGE BOX-NUMBER))))
  1088.  
  1089. ;;; Call this to idiot-proofly set the dimensions of the page or boxes.
  1090. (DEFUN SET-PRINTER-DIMENSIONS (PAGE-WIDTH &OPTIONAL PAGE-HEIGHT BOX-MAX-WIDTH
  1091.                    BOX-MAX-HEIGHT)
  1092.   ;; first set the width idiot-proofly.
  1093.   (COND ((AND (NULL PAGE-WIDTH) (NULL BOX-MAX-WIDTH))      ;neither width given
  1094.      (SETQ *PAGE-WIDTH* 100.)
  1095.      (SETQ *BOX-MAXIMUM-WIDTH* (- *PAGE-WIDTH* *BOX-IDENTIFIER-WIDTH*)))
  1096.     ((NULL BOX-MAX-WIDTH)          ;only page width given
  1097.      (SETQ *PAGE-WIDTH* PAGE-WIDTH)
  1098.      (SETQ *BOX-MAXIMUM-WIDTH* (- *PAGE-WIDTH* *BOX-IDENTIFIER-WIDTH*)))
  1099.     ((NULL PAGE-WIDTH)          ;only box width given
  1100.      (SETQ *BOX-MAXIMUM-WIDTH* BOX-MAX-WIDTH)
  1101.      (SETQ *PAGE-WIDTH* (+ *BOX-IDENTIFIER-WIDTH* *BOX-MAXIMUM-WIDTH*)))
  1102.     (T (IF (> BOX-MAX-WIDTH          ;both given - check consistency
  1103.           (- PAGE-WIDTH *BOX-IDENTIFIER-WIDTH*))
  1104.            (FERROR NIL "~
  1105. The values you have given for page width, ~D, and maximum box width, ~D, are
  1106. inconsistent with each other.  The maximum box width must be at least ~D less
  1107. than the page width." PAGE-WIDTH BOX-MAX-WIDTH *BOX-IDENTIFIER-WIDTH*)
  1108.          (SETQ *PAGE-WIDTH* PAGE-WIDTH
  1109.            *BOX-MAXIMUM-WIDTH* BOX-MAX-WIDTH))))
  1110.   (COND ((AND (NULL PAGE-HEIGHT) (NULL BOX-MAX-HEIGHT))      ;neither height given
  1111.      (SETQ *PAGE-HEIGHT* 70.)
  1112.      (SETQ *BOX-MAXIMUM-HEIGHT* (1- (- *PAGE-HEIGHT* *INTER-BOX-SPACING*))))
  1113.     ((NULL BOX-MAX-HEIGHT)          ;only page height given
  1114.      (SETQ *PAGE-HEIGHT* PAGE-HEIGHT)
  1115.      (SETQ *BOX-MAXIMUM-HEIGHT* (1- (- *PAGE-HEIGHT* *INTER-BOX-SPACING*))))
  1116.     ((NULL PAGE-HEIGHT)          ;only box height given
  1117.      (SETQ *BOX-MAXIMUM-HEIGHT* BOX-MAX-HEIGHT)
  1118.      (SETQ *PAGE-HEIGHT* (+ 1 *INTER-BOX-SPACING* *BOX-MAXIMUM-HEIGHT*)))
  1119.     (T (IF (> BOX-MAX-HEIGHT      ;both given - check consistency
  1120.           (1- (- PAGE-HEIGHT *INTER-BOX-SPACING*)))
  1121.            (FERROR NIL "~
  1122. The values you have given for page height, ~D, and maximum box height, ~D, are
  1123. inconsistent with each other.  The maximum box height IRst be at least ~D less
  1124. than the page height."
  1125.                PAGE-HEIGHT BOX-MAX-HEIGHT
  1126.                (1+ *INTER-BOX-SPACING*))
  1127.          (SETQ *PAGE-HEIGHT* PAGE-HEIGHT
  1128.            *BOX-MAXIMUM-HEIGHT* BOX-MAX-HEIGHT)))))
  1129.